package com.yang.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;

/**
 * @author zhy
 * 
 */
public class MyBatisUtils {
	/**
	 * 运行期获取MyBatis执行的SQL及参数
	 *
	 * @param id                Mapper xml 文件里的select Id
	 * @param parameterMap      参数
	 * @param sqlSessionFactory
	 * @return
	 */
	public static MyBatisSql getMyBatisSql(String id, Map<String, Object> parameterMap,
			SqlSessionFactory sqlSessionFactory) {
		if (StringUtils.isBlank(id) || sqlSessionFactory == null) {
			return null;
		}
		return getMysql(id, parameterMap, sqlSessionFactory);
	}

	/**
	 * @param clazz             Mapper.class
	 * @param methodName        方法
	 * @param parameterMap      参数
	 * @param sqlSessionFactory
	 * @return
	 */
	public static <T> MyBatisSql getMyBatisSql(Class<T> clazz, String methodName, Map<String, Object> parameterMap,
			SqlSessionFactory sqlSessionFactory) {
		if (clazz == null || StringUtils.isBlank(methodName) || sqlSessionFactory == null) {
			return null;
		}
		String id = clazz.getName() + "." + methodName.trim();
		return getMysql(id, parameterMap, sqlSessionFactory);
	}

	private static MyBatisSql getMysql(String id, Map<String, Object> parameterMap,
			SqlSessionFactory sqlSessionFactory) {
		MyBatisSql myBatisSql = new MyBatisSql();
		myBatisSql.setSqlId(id);
		myBatisSql.setParameterMap(parameterMap);
		MappedStatement ms = sqlSessionFactory.getConfiguration().getMappedStatement(id);
		BoundSql boundSql = ms.getBoundSql(parameterMap);
		myBatisSql.setSql(boundSql.getSql());
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		Configuration configuration = sqlSessionFactory.getConfiguration();
		if (parameterMappings != null) {
			Object[] parameterArray = new Object[parameterMappings.size()];
			ParameterMapping parameterMapping = null;
			Object value = null;
			Object parameterObject = null;
			MetaObject metaObject = null;
			PropertyTokenizer prop = null;
			String propertyName = null;
			String[] names = null;
			for (int i = 0; i < parameterMappings.size(); i++) {
				parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					propertyName = parameterMapping.getProperty();
					names = propertyName.split("\\.");
					if (propertyName.indexOf(".") != -1 && names.length == 2) {
						parameterObject = parameterMap.get(names[0]);
						propertyName = names[1];
					} else if (propertyName.indexOf(".") != -1 && names.length == 3) {
						parameterObject = parameterMap.get(names[0]); // map
						if (parameterObject instanceof Map) {
							parameterObject = ((Map) parameterObject).get(names[1]);
						}
						propertyName = names[2];
					} else {
						parameterObject = parameterMap.get(propertyName);
					}
					metaObject = parameterMap == null ? null
							: MetaObject.forObject(parameterObject, configuration.getObjectFactory(),
									configuration.getObjectWrapperFactory());
					prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (ms.getConfiguration().getTypeHandlerRegistry()
							.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
							&& boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = MetaObject.forObject(value, configuration.getObjectFactory(),
									configuration.getObjectWrapperFactory());
						}
					} else {
						value = metaObject == null ? null : metaObject.getValue(propertyName);
					}
					parameterArray[i] = value;
				}
			}
			myBatisSql.setParameters(parameterArray);
		}
		return myBatisSql;
	}

	/**
	 * 
	 * MyBatisSql 封装的类
	 * 
	 * @author zhy
	 *
	 */
	public static class MyBatisSql {

		/**
		 * 运行期 sql
		 */
		private String sql;

		/**
		 * 执行sqlID
		 */
		private String sqlId;
		/**
		 * 执行该sql传的参数
		 */
		private Map<String, Object> parameterMap;

		/**
		 * 参数 数组
		 */
		private Object[] parameters;

		public void setSql(String sql) {
			this.sql = sql;
		}

		/**
		 * 运行的sql 语句
		 * 
		 * @return
		 */
		public String getSql() {
			return sql;
		}

		public void setParameters(Object[] parameters) {
			this.parameters = parameters;
		}

		public Object[] getParameters() {
			return parameters;
		}

		public String getSqlId() {
			return sqlId;
		}

		public void setSqlId(String sqlId) {
			this.sqlId = sqlId;
		}

		public Map<String, Object> getParameterMap() {
			return parameterMap;
		}

		public void setParameterMap(Map<String, Object> parameterMap) {
			this.parameterMap = parameterMap;
		}

		/**
		 * @return
		 */
		public String getParameterStr() {
			StringBuilder sb = new StringBuilder();
			sb.append("sqlId:").append(this.getSqlId()).append("\n");
			if (this.getParameterMap() != null && !this.getParameterMap().isEmpty()) {
				sb.append("parameter:");
				for (Entry<String, Object> en : this.getParameterMap().entrySet()) {
					sb.append("\t").append(en.getKey()).append(" = ").append(en.getValue()).append("\n");
				}
			}
			return sb.toString();
		}

		/**
		 * 最终在MySql中执行的语句
		 */
		@Override
		public String toString() {
			if (parameters == null || sql == null) {
				return "";
			}
			List<Object> parametersArray = Arrays.asList(parameters);
			List<Object> list = new ArrayList<Object>(parametersArray);
			while (sql.indexOf("?") != -1 && list.size() > 0 && parameters.length > 0) {
				sql = sql.replaceFirst("\\?", list.get(0).toString());
				list.remove(0);
			}
			return sql.replaceAll("(\r?\n(\\s*\r?\n)+)", "\r\n");
		}
	}

}
